Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
79.17% covered (warning)
79.17%
19 / 24
CRAP
82.56% covered (warning)
82.56%
161 / 195
CategoryAccessRepository
0.00% covered (danger)
0.00%
0 / 1
79.17% covered (warning)
79.17%
19 / 24
36.09
82.56% covered (warning)
82.56%
161 / 195
 getGrantedUserGroups
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 revokeAccess
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
10 / 10
 revokeAccessToGroups
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
 getGrantedCategoryQB
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
9 / 9
 getGrantedChildrenIds
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getGrantedChildrenCodes
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 getRevokedCategoryQB
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 18
 getCategoryIdsWithExistingAccess
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
9 / 9
 getGrantedCategoryIds
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
12 / 12
 getGrantedCategoryCodes
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
7 / 7
 getRevokedCategoryIds
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 6
 getRevokedAttributeIds
0.00% covered (danger)
0.00%
0 / 1
2.00
0.00% covered (danger)
0.00%
0 / 8
 getGrantedUserGroupsForEntityWithValues
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
13 / 13
 isOwner
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
9 / 9
 areAllCategoryCodesGranted
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
15 / 15
 isCategoriesGranted
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
11 / 11
 getAccessField
0.00% covered (danger)
0.00%
0 / 1
2.02
83.33% covered (warning)
83.33%
5 / 6
 getAccessColumn
0.00% covered (danger)
0.00%
0 / 1
2.02
83.33% covered (warning)
83.33%
5 / 6
 hydrateAsIds
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
4 / 4
 setTableNameBuilder
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 getTableName
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getIdentifierProperties
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 findOneByIdentifier
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
11 / 11
 getGrantedChildrenQB
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
14 / 14
<?php
/*
 * This file is part of the Akeneo PIM Enterprise Edition.
 *
 * (c) 2014 Akeneo SAS (http://www.akeneo.com)
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace Akeneo\Pim\Permission\Bundle\Entity\Repository;
use Akeneo\Pim\Enrichment\Component\Product\Model\EntityWithValuesInterface;
use Akeneo\Pim\Permission\Component\Attributes;
use Akeneo\Tool\Bundle\StorageUtilsBundle\Doctrine\TableNameBuilder;
use Akeneo\Tool\Component\Classification\Model\CategoryInterface;
use Akeneo\Tool\Component\StorageUtils\Repository\IdentifiableObjectRepositoryInterface;
use Akeneo\UserManagement\Component\Model\Group;
use Akeneo\UserManagement\Component\Model\GroupInterface;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\User\UserInterface;
/**
 * Category access repository
 *
 * @author Julien Janvier <julien.janvier@akeneo.com>
 *
 * TODO: It must be entity agnostic for PIM-4292.
 */
class CategoryAccessRepository extends EntityRepository implements IdentifiableObjectRepositoryInterface
{
    /** @var TableNameBuilder */
    protected $tableNameBuilder;
    /**
     * Get user groups that have the specified access to a category
     *
     * @param CategoryInterface $category
     * @param string            $accessLevel
     *
     * @return GroupInterface[]
     */
    public function getGrantedUserGroups(CategoryInterface $category, $accessLevel)
    {
        $qb = $this->createQueryBuilder('a');
        $qb
            ->select('g')
            ->innerJoin(Group::class, 'g', 'WITH', 'a.userGroup = g.id')
            ->where('a.category = :category')
            ->andWhere($qb->expr()->eq(sprintf('a.%s', $this->getAccessField($accessLevel)), true))
            ->setParameter('category', $category);
        return $qb->getQuery()->getResult();
    }
    /**
     * Revoke access to a category
     * If excluded user groups are provided, access will not be revoked for these group
     *
     * @param CategoryInterface $category
     * @param Group[]           $excludedGroups
     *
     * @return int
     */
    public function revokeAccess(CategoryInterface $category, array $excludedGroups = [])
    {
        $qb = $this->createQueryBuilder('a');
        $qb
            ->where('a.category = :category')
            ->setParameter('category', $category);
        if (!empty($excludedGroups)) {
            $qb
                ->andWhere($qb->expr()->notIn('a.userGroup', ':excludedGroups'))
                ->setParameter('excludedGroups', $excludedGroups);
        }
        $entities = $qb->getQuery()->execute();
        foreach ($entities as $categoryAccess) {
            $this->_em->remove($categoryAccess);
        }
        return count($entities);
    }
    /**
     * Remove access to a group for all categories
     *
     * @param Group[] $groups
     *
     * @return int
     */
    public function revokeAccessToGroups(array $groups)
    {
        $qb = $this->createQueryBuilder('a');
        $qb
            ->where($qb->expr()->in('a.userGroup', ':groups'))
            ->setParameter('groups', $groups);
        $entities = $qb->getQuery()->execute();
        foreach ($entities as $categoryAccess) {
            $this->_em->remove($categoryAccess);
        }
        return count($entities);
    }
    /**
     * Get granted category query builder
     *
     * @param UserInterface $user
     * @param string        $accessLevel
     *
     * @return \Doctrine\ORM\QueryBuilder
     */
    public function getGrantedCategoryQB(UserInterface $user, $accessLevel)
    {
        $qb = $this->createQueryBuilder('ca');
        $qb
            ->andWhere($qb->expr()->in('ca.userGroup', ':groups'))
            ->setParameter('groups', $user->getGroups()->toArray())
            ->andWhere($qb->expr()->eq('ca.'.$this->getAccessField($accessLevel), true))
            ->resetDQLParts(['select'])
            ->innerJoin('ca.category', 'c', 'c.id')
            ->select('c.id')
            ->distinct(true);
        return $qb;
    }
    /**
     * Get granted categories ids from the provided category
     *
     * @param CategoryInterface $category
     * @param UserInterface     $user
     * @param string            $accessLevel
     *
     * @return array
     */
    public function getGrantedChildrenIds(CategoryInterface $category, UserInterface $user, $accessLevel)
    {
        $qb = $this->getGrantedChildrenQB($category, $user, $accessLevel);
        return $this->hydrateAsIds($qb);
    }
    /**
     * Get granted categories codes from the provided category
     *
     * @param CategoryInterface $category
     * @param UserInterface     $user
     * @param string            $accessLevel
     *
     * @return array
     */
    public function getGrantedChildrenCodes(CategoryInterface $category, UserInterface $user, $accessLevel)
    {
        $qb = $this->getGrantedChildrenQB($category, $user, $accessLevel)
            ->resetDQLParts(['select'])
            ->select('DISTINCT c.code');
        return array_map(
            function ($row) {
                return $row['code'];
            },
            $qb->getQuery()->getArrayResult()
        );
    }
    /**
     * Get revoked category query builder
     *
     * @param UserInterface $user
     * @param string        $accessLevel
     *
     * @return \Doctrine\DBAL\Query\QueryBuilder
     */
    public function getRevokedCategoryQB(UserInterface $user, $accessLevel)
    {
        // get group ids
        $groupIds = array_map(
            function (Group $group) {
                return $group->getId();
            },
            $user->getGroups()->toArray()
        );
        $categoryTable = $this->getTableName('pim_catalog.entity.category.class');
        $categoryAccessTable = $this->getTableName('pimee_security.entity.product_category_access.class');
        $conn = $this->_em->getConnection();
        $qb = $conn->createQueryBuilder();
        $qb
            ->select('*')
            ->from($categoryTable, 'c')
            ->leftJoin('c', $categoryAccessTable, 'ca', 'ca.category_id = c.id')
            ->andWhere(
                $qb->expr()->orX(
                    $qb->expr()->andX(
                        $qb->expr()->neq('ca.'.$this->getAccessField($accessLevel), true),
                        $qb->expr()->in('ca.user_group_id', $groupIds)
                    ),
                    $qb->expr()->isNull('ca.'.$this->getAccessField($accessLevel))
                )
            );
        return $qb;
    }
    /**
     * Get granted category ids
     *
     * @param Group[] $groups
     * @param int[]   $categoryIds
     *
     * @return int[]
     */
    public function getCategoryIdsWithExistingAccess($groups, $categoryIds)
    {
        $qb = $this->createQueryBuilder('ca');
        $qb
            ->andWhere($qb->expr()->in('ca.userGroup', ':groups'))
            ->setParameter('groups', $groups)
            ->andWhere($qb->expr()->in('c.id', ':categories'))
            ->setParameter('categories', $categoryIds)
            ->resetDQLParts(['select'])
            ->innerJoin('ca.category', 'c', 'c.id')
            ->select('c.id');
        return $this->hydrateAsIds($qb);
    }
    /**
     * Returns granted categories ids
     *
     * @param UserInterface $user
     * @param string        $accessLevel
     *
     * @return int[]
     */
    public function getGrantedCategoryIds(UserInterface $user, $accessLevel)
    {
        $categoryAccessTable = $this->_class->table['name'];
        $userGroupsTable = $this->getTableName('pim_user.entity.user.class', 'groups');
        $pdo = $this->_em->getConnection()->getWrappedConnection();
        $stmt = $pdo->prepare(
            sprintf(
                "SELECT ca.category_id
                    FROM %s ca
                    JOIN %s ug
                        ON ug.group_id = ca.user_group_id
                    WHERE ug.user_id = :user_id
                      AND ca.%s = 1",
                $categoryAccessTable,
                $userGroupsTable,
                $this->getAccessColumn($accessLevel)
            )
        );
        $userId = $user->getId();
        $stmt->bindParam('user_id', $userId);
        $stmt->execute();
        $ids = $stmt->fetchAll(\PDO::FETCH_COLUMN, 'ca.id');
        return array_map('intval', $ids);
    }
    /**
     * Returns granted category codes
     *
     * @param UserInterface $user
     * @param string        $accessLevel
     *
     * @return string[]
     */
    public function getGrantedCategoryCodes(UserInterface $user, $accessLevel)
    {
        $qb = $this->getGrantedCategoryQB($user, $accessLevel)
            ->resetDQLParts(['select'])
            ->select('c.code');
        return array_map(
            function ($row) {
                return $row['code'];
            },
            $qb->getQuery()->getArrayResult()
        );
    }
    /**
     * Returns revoked category ids
     *
     * @param UserInterface $user
     * @param string        $accessLevel
     *
     * @return int[]
     */
    public function getRevokedCategoryIds(UserInterface $user, $accessLevel)
    {
        $qb = $this->getRevokedCategoryQB($user, $accessLevel);
        $qb->select('DISTINCT c.id');
        return array_map(
            function ($row) {
                return $row['id'];
            },
            $qb->execute()->fetchAll()
        );
    }
    /**
     * Returns revoked category ids
     *
     * @param UserInterface $user
     * @param string        $accessLevel
     *
     * @return int[]
     */
    public function getRevokedAttributeIds(UserInterface $user, $accessLevel)
    {
        $attTable = $this->getTableName('pim_catalog.entity.category.class');
        $qb = $this->getRevokedCategoryQB($user, $accessLevel);
        $qb
            ->select('DISTINCT a.id')
            ->innerJoin('c', $attTable, 'a', 'a.category_id = c.id');
        return array_map(
            function ($row) {
                return $row['id'];
            },
            $qb->execute()->fetchAll()
        );
    }
    /**
     * Get the granted user groups for a product
     *
     * @param EntityWithValuesInterface $entityWithValues the product
     * @param string                    $accessLevel      the expected access level
     *
     * @return array
     */
    public function getGrantedUserGroupsForEntityWithValues(EntityWithValuesInterface $entityWithValues, $accessLevel)
    {
        $categories = $entityWithValues->getCategories();
        if (0 === count($categories)) {
            return [];
        }
        $categoryIds = [];
        foreach ($categories as $category) {
            $categoryIds[] = $category->getId();
        }
        $qb = $this->createQueryBuilder('ca');
        $qb->where($qb->expr()->in('ca.category', $categoryIds));
        $qb->andWhere($qb->expr()->eq('ca.'.$this->getAccessField($accessLevel), true));
        $qb->leftJoin('ca.userGroup', 'ug');
        $qb->select('DISTINCT (ug.id) as id, ug.name');
        $groups = $qb->getQuery()->execute([], AbstractQuery::HYDRATE_ARRAY);
        return $groups;
    }
    /**
     * Indicates whether a user is the owner of any categories
     *
     * @param UserInterface $user
     *
     * @return bool
     */
    public function isOwner(UserInterface $user)
    {
        $qb = $this->createQueryBuilder('o');
        $qb
            ->select('o.id')
            ->where(
                $qb->expr()->in('o.userGroup', ':groups')
            )
            ->setParameter('groups', $user->getGroups()->toArray())
            ->andWhere($qb->expr()->eq('o.'.$this->getAccessField(Attributes::OWN_PRODUCTS), true))
            ->setMaxResults(1);
        $result = $qb->getQuery()->getResult(AbstractQuery::HYDRATE_SCALAR);
        return (bool) count($result);
    }
    /**
     * Check if all categories are granted to user
     *
     * @param UserInterface $user
     * @param string        $accessLevel
     * @param array         $categoryCodes
     *
     * @return bool
     */
    public function areAllCategoryCodesGranted(UserInterface $user, $accessLevel, array $categoryCodes)
    {
        $groupIds = array_map(
            function (GroupInterface $group) {
                return $group->getId();
            },
            $user->getGroups()->toArray()
        );
        $qb = $this->_em->createQueryBuilder()
            ->select('COUNT(DISTINCT c.id)')
            ->from($this->_entityName, 'ca');
        $qb
            ->innerJoin('ca.category', 'c', 'c.id')
            ->where($qb->expr()->in('ca.userGroup', ':groups'))
            ->setParameter('groups', $groupIds)
            ->andWhere($qb->expr()->eq('ca.' . $this->getAccessField($accessLevel), true))
            ->andWhere($qb->expr()->in('c.code', ':codes'))
            ->setParameter('codes', $categoryCodes);
        $numberOfGranted = $qb->getQuery()->getSingleScalarResult();
        return count($categoryCodes) === (int) $numberOfGranted;
    }
    /**
     * Check if categories are granted to user
     *
     * @param UserInterface $user
     * @param string        $accessLevel
     * @param array         $categoryIds
     *
     * @return bool
     */
    public function isCategoriesGranted(UserInterface $user, $accessLevel, array $categoryIds)
    {
        $qb = $this->_em->createQueryBuilder()
            ->select('COUNT(c.id)')
            ->from($this->_entityName, 'ca');
        $qb->andWhere($qb->expr()->in('ca.userGroup', ':groups'))
            ->setParameter('groups', $user->getGroups()->toArray())
            ->andWhere($qb->expr()->eq('ca.'.$this->getAccessField($accessLevel), true))
            ->andWhere($qb->expr()->in('c.id', ':categoryIds'))
            ->setParameter('categoryIds', $categoryIds)
            ->innerJoin('ca.category', 'c', 'c.id');
        $count = $qb->getQuery()->getSingleScalarResult();
        return $count > 0;
    }
    /**
     * Get the access field depending of access level sent
     *
     * @param string $accessLevel
     *
     * @throws \LogicException
     *
     * @return string
     */
    protected function getAccessField($accessLevel)
    {
        $mapping = [
            Attributes::OWN_PRODUCTS  => 'ownItems',
            Attributes::EDIT_ITEMS    => 'editItems',
            Attributes::VIEW_ITEMS    => 'viewItems',
        ];
        if (!isset($mapping[$accessLevel])) {
            throw new \LogicException(sprintf('"%s" access level does not exist', $accessLevel));
        }
        return $mapping[$accessLevel];
    }
    /**
     * Get the column field depending of access level sent
     *
     * @param string $accessLevel
     *
     * @throws \LogicException
     *
     * @return string
     */
    protected function getAccessColumn($accessLevel)
    {
        $mapping = [
            Attributes::OWN_PRODUCTS  => 'own_items',
            Attributes::EDIT_ITEMS    => 'edit_items',
            Attributes::VIEW_ITEMS    => 'view_items',
        ];
        if (!isset($mapping[$accessLevel])) {
            throw new \LogicException(sprintf('"%s" access level does not exist', $accessLevel));
        }
        return $mapping[$accessLevel];
    }
    /**
     * Execute a query builder and hydrate it as an array of database identifiers
     *
     * @param QueryBuilder $qb
     *
     * @return int[]
     */
    protected function hydrateAsIds(QueryBuilder $qb)
    {
        return array_map(
            function ($row) {
                return $row['id'];
            },
            $qb->getQuery()->getArrayResult()
        );
    }
    /**
     * Set table name builder
     *
     * @param TableNameBuilder $tableNameBuilder
     *
     * @return CategoryAccessRepository
     */
    public function setTableNameBuilder(TableNameBuilder $tableNameBuilder)
    {
        $this->tableNameBuilder = $tableNameBuilder;
        return $this;
    }
    /**
     * Get table name of entity defined
     *
     * @param string      $classParam
     * @param string|null $targetEntity
     *
     * @return string
     */
    protected function getTableName($classParam, $targetEntity = null)
    {
        return $this->tableNameBuilder->getTableName($classParam, $targetEntity);
    }
    /**
     * {@inheritdoc}
     */
    public function getIdentifierProperties()
    {
        return ['category', 'user_group'];
    }
    /**
     * {@inheritdoc}
     */
    public function findOneByIdentifier($identifier)
    {
        list($categoryCode, $userGroupName) = explode('.', $identifier, 2);
        $associationMappings = $this->_em->getClassMetadata($this->_entityName)->getAssociationMappings();
        $categoryClass = $associationMappings['category']['targetEntity'];
        $qb = $this->createQueryBuilder('a')
            ->innerJoin(Group::class, 'g', 'WITH', 'a.userGroup = g.id')
            ->innerJoin($categoryClass, 'c', 'WITH', 'a.category = c.id')
            ->where('c.code = :categoryCode')
            ->andWhere('g.name = :userGroupName')
            ->setParameter('categoryCode', $categoryCode)
            ->setParameter('userGroupName', $userGroupName);
        return $qb->getQuery()->getOneOrNullResult();
    }
    /**
     * Get granted children categories query builder
     *
     * @param CategoryInterface $category
     * @param UserInterface     $user
     * @param string            $accessLevel
     *
     * @return \Doctrine\ORM\QueryBuilder
     */
    protected function getGrantedChildrenQB(CategoryInterface $category, UserInterface $user, $accessLevel)
    {
        $qb = $this->createQueryBuilder('ca');
        $qb
            ->select('DISTINCT c.id')
            ->innerJoin('ca.category', 'c', 'c.id')
            ->andWhere($qb->expr()->in('ca.userGroup', ':groups'))
            ->andWhere($qb->expr()->eq('ca.'.$this->getAccessField($accessLevel), true))
            ->andWhere(
                $qb->expr()->orX(
                    $qb->expr()->andX(
                        $qb->expr()->lt('c.right', $category->getRight()),
                        $qb->expr()->gt('c.left', $category->getLeft()),
                        $qb->expr()->eq('c.root', $category->getRoot())
                    ),
                    $qb->expr()->eq('c.id', $category->getId())
                )
            );
        $qb->setParameter('groups', $user->getGroups()->toArray());
        return $qb;
    }
}